#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
YAML文件处理器

该模块提供了YAML文件的读写和处理功能。

创建时间: 2023-07-12
作者: Python Tools Team
"""

import yaml
from typing import Optional, Union, Dict, Any, List, Callable
from pathlib import Path
import io

from modules.logger import LogManager
from modules.exception import exception_handler, FileException
from modules.fileio.file_manager import FileManager


class YAMLFileHandler:
    """
    YAML文件处理器
    
    该类提供了YAML文件的读写和处理功能，支持多种格式和选项。
    """
    
    def __init__(self, file_manager: Optional[FileManager] = None, logger=None):
        """
        初始化YAML文件处理器
        
        Args:
            file_manager: 文件管理器实例，如果为None则创建新实例
            logger: 日志记录器，如果为None则使用默认日志记录器
        """
        self.file_manager = file_manager or FileManager()
        self.logger = logger or LogManager.get_both_logger(name="yaml_handler")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def read(self, file_path: Union[str, Path], encoding: str = 'utf-8') -> Any:
        """
        读取YAML文件
        
        Args:
            file_path: YAML文件路径
            encoding: 文件编码，默认为utf-8
        
        Returns:
            Any: YAML数据，通常为字典或列表
        
        Raises:
            FileException: 文件不存在或读取失败时抛出
            yaml.YAMLError: YAML解析失败时抛出
        
        Examples:
            >>> yaml_handler = YAMLFileHandler()
            >>> data = yaml_handler.read('config.yaml')
            >>> print(data['version'])
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        content = self.file_manager.read_text(file_path, encoding=encoding)
        
        try:
            data = yaml.safe_load(content)
        except yaml.YAMLError as e:
            self.logger.error(f"YAML解析失败: {e}")
            raise
        
        self.logger.debug(f"已读取YAML文件: {file_path}")
        return data
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def read_all(self, file_path: Union[str, Path], encoding: str = 'utf-8') -> List[Any]:
        """
        读取包含多个YAML文档的文件
        
        Args:
            file_path: YAML文件路径
            encoding: 文件编码，默认为utf-8
        
        Returns:
            List[Any]: YAML文档列表
        
        Raises:
            FileException: 文件不存在或读取失败时抛出
            yaml.YAMLError: YAML解析失败时抛出
        
        Examples:
            >>> yaml_handler = YAMLFileHandler()
            >>> documents = yaml_handler.read_all('multi_doc.yaml')
            >>> for doc in documents:
            >>>     print(doc)
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        content = self.file_manager.read_text(file_path, encoding=encoding)
        
        try:
            documents = list(yaml.safe_load_all(content))
        except yaml.YAMLError as e:
            self.logger.error(f"YAML解析失败: {e}")
            raise
        
        self.logger.debug(f"已读取多文档YAML文件: {file_path}")
        return documents
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def write(self, file_path: Union[str, Path], data: Any, encoding: str = 'utf-8', 
              default_flow_style: bool = False, sort_keys: bool = False, 
              create_dirs: bool = True) -> bool:
        """
        写入数据到YAML文件
        
        Args:
            file_path: YAML文件路径
            data: 要写入的数据
            encoding: 文件编码，默认为utf-8
            default_flow_style: 是否使用流式风格，默认为False
            sort_keys: 是否对字典键进行排序，默认为False
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 写入成功返回True
        
        Raises:
            FileException: 写入失败时抛出
        
        Examples:
            >>> yaml_handler = YAMLFileHandler()
            >>> data = {'version': '1.0', 'settings': {'debug': True}}
            >>> yaml_handler.write('config.yaml', data)
        """
        file_path = Path(file_path)
        
        try:
            yaml_str = yaml.dump(data, default_flow_style=default_flow_style, 
                               sort_keys=sort_keys, allow_unicode=True)
        except yaml.YAMLError as e:
            self.logger.error(f"YAML序列化失败: {e}")
            raise
        
        # 写入文件
        self.file_manager.write_text(file_path, yaml_str, encoding=encoding, create_dirs=create_dirs)
        
        self.logger.debug(f"已写入YAML文件: {file_path}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def write_all(self, file_path: Union[str, Path], documents: List[Any], encoding: str = 'utf-8', 
                 default_flow_style: bool = False, sort_keys: bool = False, 
                 create_dirs: bool = True) -> bool:
        """
        写入多个文档到YAML文件
        
        Args:
            file_path: YAML文件路径
            documents: 要写入的文档列表
            encoding: 文件编码，默认为utf-8
            default_flow_style: 是否使用流式风格，默认为False
            sort_keys: 是否对字典键进行排序，默认为False
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 写入成功返回True
        
        Raises:
            FileException: 写入失败时抛出
        
        Examples:
            >>> yaml_handler = YAMLFileHandler()
            >>> docs = [
            >>>     {'name': 'doc1', 'version': '1.0'},
            >>>     {'name': 'doc2', 'version': '2.0'}
            >>> ]
            >>> yaml_handler.write_all('multi_doc.yaml', docs)
        """
        file_path = Path(file_path)
        
        try:
            yaml_str = yaml.dump_all(documents, default_flow_style=default_flow_style, 
                                   sort_keys=sort_keys, allow_unicode=True)
        except yaml.YAMLError as e:
            self.logger.error(f"YAML序列化失败: {e}")
            raise
        
        # 写入文件
        self.file_manager.write_text(file_path, yaml_str, encoding=encoding, create_dirs=create_dirs)
        
        self.logger.debug(f"已写入多文档YAML文件: {file_path}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def update(self, file_path: Union[str, Path], data: Dict[str, Any], 
              create_if_not_exists: bool = True, encoding: str = 'utf-8', 
              default_flow_style: bool = False, sort_keys: bool = False) -> bool:
        """
        更新YAML文件中的数据
        
        Args:
            file_path: YAML文件路径
            data: 要更新的数据字典
            create_if_not_exists: 如果文件不存在，是否创建，默认为True
            encoding: 文件编码，默认为utf-8
            default_flow_style: 是否使用流式风格，默认为False
            sort_keys: 是否对字典键进行排序，默认为False
        
        Returns:
            bool: 更新成功返回True
        
        Raises:
            FileException: 更新失败时抛出
        
        Examples:
            >>> yaml_handler = YAMLFileHandler()
            >>> yaml_handler.update('config.yaml', {'settings': {'debug': False}})
        """
        file_path = Path(file_path)
        
        # 如果文件存在，先读取现有数据
        if file_path.exists():
            existing_data = self.read(file_path, encoding=encoding)
            if not isinstance(existing_data, dict):
                existing_data = {}
        elif create_if_not_exists:
            existing_data = {}
        else:
            raise FileException(f"文件不存在: {file_path}")
        
        # 递归更新字典
        self._update_dict(existing_data, data)
        
        # 写入更新后的数据
        return self.write(file_path, existing_data, encoding=encoding, 
                        default_flow_style=default_flow_style, sort_keys=sort_keys)
    
    def _update_dict(self, target: Dict[str, Any], source: Dict[str, Any]) -> None:
        """
        递归更新字典
        
        Args:
            target: 目标字典
            source: 源字典
        """
        for key, value in source.items():
            if key in target and isinstance(target[key], dict) and isinstance(value, dict):
                self._update_dict(target[key], value)
            else:
                target[key] = value
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def merge(self, file_paths: List[Union[str, Path]], output_path: Union[str, Path], 
             encoding: str = 'utf-8', default_flow_style: bool = False, 
             sort_keys: bool = False) -> bool:
        """
        合并多个YAML文件
        
        Args:
            file_paths: YAML文件路径列表
            output_path: 输出文件路径
            encoding: 文件编码，默认为utf-8
            default_flow_style: 是否使用流式风格，默认为False
            sort_keys: 是否对字典键进行排序，默认为False
        
        Returns:
            bool: 合并成功返回True
        
        Raises:
            FileException: 合并失败时抛出
        
        Examples:
            >>> yaml_handler = YAMLFileHandler()
            >>> yaml_handler.merge(['config1.yaml', 'config2.yaml'], 'merged_config.yaml')
        """
        merged_data = {}
        
        for file_path in file_paths:
            file_path = Path(file_path)
            if not file_path.exists():
                self.logger.warning(f"文件不存在，跳过: {file_path}")
                continue
            
            data = self.read(file_path, encoding=encoding)
            if not isinstance(data, dict):
                self.logger.warning(f"文件不是字典格式，跳过: {file_path}")
                continue
            
            self._update_dict(merged_data, data)
        
        return self.write(output_path, merged_data, encoding=encoding, 
                        default_flow_style=default_flow_style, sort_keys=sort_keys)
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def get_value(self, data: Dict[str, Any], key_path: str, default: Any = None) -> Any:
        """
        获取嵌套字典中的值
        
        Args:
            data: YAML数据字典
            key_path: 键路径，使用点号分隔，如'settings.debug'
            default: 如果键不存在，返回的默认值
        
        Returns:
            Any: 键对应的值
        
        Examples:
            >>> yaml_handler = YAMLFileHandler()
            >>> data = yaml_handler.read('config.yaml')
            >>> debug = yaml_handler.get_value(data, 'settings.debug', False)
            >>> print(debug)
        """
        keys = key_path.split('.')
        current = data
        
        for key in keys:
            if not isinstance(current, dict) or key not in current:
                return default
            current = current[key]
        
        return current
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def set_value(self, data: Dict[str, Any], key_path: str, value: Any) -> Dict[str, Any]:
        """
        设置嵌套字典中的值
        
        Args:
            data: YAML数据字典
            key_path: 键路径，使用点号分隔，如'settings.debug'
            value: 要设置的值
        
        Returns:
            Dict[str, Any]: 更新后的字典
        
        Examples:
            >>> yaml_handler = YAMLFileHandler()
            >>> data = yaml_handler.read('config.yaml')
            >>> data = yaml_handler.set_value(data, 'settings.debug', True)
            >>> yaml_handler.write('config.yaml', data)
        """
        keys = key_path.split('.')
        current = data
        
        # 遍历除最后一个键外的所有键
        for key in keys[:-1]:
            if key not in current or not isinstance(current[key], dict):
                current[key] = {}
            current = current[key]
        
        # 设置最后一个键的值
        current[keys[-1]] = value
        
        return data
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def delete_value(self, data: Dict[str, Any], key_path: str) -> Dict[str, Any]:
        """
        删除嵌套字典中的值
        
        Args:
            data: YAML数据字典
            key_path: 键路径，使用点号分隔，如'settings.debug'
        
        Returns:
            Dict[str, Any]: 更新后的字典
        
        Examples:
            >>> yaml_handler = YAMLFileHandler()
            >>> data = yaml_handler.read('config.yaml')
            >>> data = yaml_handler.delete_value(data, 'settings.debug')
            >>> yaml_handler.write('config.yaml', data)
        """
        keys = key_path.split('.')
        current = data
        
        # 遍历除最后一个键外的所有键
        for key in keys[:-1]:
            if key not in current or not isinstance(current[key], dict):
                # 键不存在，无需删除
                return data
            current = current[key]
        
        # 删除最后一个键
        if keys[-1] in current:
            del current[keys[-1]]
        
        return data
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def filter_keys(self, data: Dict[str, Any], include_keys: List[str] = None, 
                   exclude_keys: List[str] = None) -> Dict[str, Any]:
        """
        筛选字典中的键
        
        Args:
            data: YAML数据字典
            include_keys: 要包含的键列表，如果为None则包含所有键
            exclude_keys: 要排除的键列表，如果为None则不排除任何键
        
        Returns:
            Dict[str, Any]: 筛选后的字典
        
        Examples:
            >>> yaml_handler = YAMLFileHandler()
            >>> data = yaml_handler.read('config.yaml')
            >>> filtered = yaml_handler.filter_keys(data, include_keys=['version', 'settings'])
            >>> print(filtered)
        """
        if not isinstance(data, dict):
            return data
        
        result = {}
        
        for key, value in data.items():
            # 检查是否包含/排除该键
            if (include_keys is None or key in include_keys) and \
               (exclude_keys is None or key not in exclude_keys):
                # 如果值是字典，递归筛选
                if isinstance(value, dict):
                    result[key] = self.filter_keys(value, include_keys, exclude_keys)
                else:
                    result[key] = value
        
        return result
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def flatten(self, data: Dict[str, Any], separator: str = '.') -> Dict[str, Any]:
        """
        将嵌套字典扁平化
        
        Args:
            data: YAML数据字典
            separator: 键路径分隔符，默认为'.'
        
        Returns:
            Dict[str, Any]: 扁平化后的字典
        
        Examples:
            >>> yaml_handler = YAMLFileHandler()
            >>> data = {'settings': {'debug': True, 'logging': {'level': 'INFO'}}}
            >>> flat = yaml_handler.flatten(data)
            >>> print(flat)  # {'settings.debug': True, 'settings.logging.level': 'INFO'}
        """
        result = {}
        
        def _flatten(current_data, current_path=''):
            for key, value in current_data.items():
                new_path = f"{current_path}{separator}{key}" if current_path else key
                
                if isinstance(value, dict):
                    _flatten(value, new_path)
                else:
                    result[new_path] = value
        
        _flatten(data)
        return result
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def unflatten(self, data: Dict[str, Any], separator: str = '.') -> Dict[str, Any]:
        """
        将扁平化字典还原为嵌套字典
        
        Args:
            data: 扁平化的YAML数据字典
            separator: 键路径分隔符，默认为'.'
        
        Returns:
            Dict[str, Any]: 嵌套字典
        
        Examples:
            >>> yaml_handler = YAMLFileHandler()
            >>> flat = {'settings.debug': True, 'settings.logging.level': 'INFO'}
            >>> nested = yaml_handler.unflatten(flat)
            >>> print(nested)  # {'settings': {'debug': True, 'logging': {'level': 'INFO'}}}
        """
        result = {}
        
        for key, value in data.items():
            parts = key.split(separator)
            current = result
            
            # 遍历除最后一个部分外的所有部分
            for part in parts[:-1]:
                if part not in current:
                    current[part] = {}
                current = current[part]
            
            # 设置最后一个部分的值
            current[parts[-1]] = value
        
        return result
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def to_string(self, data: Any, default_flow_style: bool = False, 
                 sort_keys: bool = False) -> str:
        """
        将数据转换为YAML字符串
        
        Args:
            data: 要转换的数据
            default_flow_style: 是否使用流式风格，默认为False
            sort_keys: 是否对字典键进行排序，默认为False
        
        Returns:
            str: YAML字符串
        
        Raises:
            yaml.YAMLError: YAML序列化失败时抛出
        
        Examples:
            >>> yaml_handler = YAMLFileHandler()
            >>> data = {'version': '1.0', 'settings': {'debug': True}}
            >>> yaml_str = yaml_handler.to_string(data)
            >>> print(yaml_str)
        """
        try:
            return yaml.dump(data, default_flow_style=default_flow_style, 
                           sort_keys=sort_keys, allow_unicode=True)
        except yaml.YAMLError as e:
            self.logger.error(f"YAML序列化失败: {e}")
            raise
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def from_string(self, yaml_str: str) -> Any:
        """
        将YAML字符串转换为数据
        
        Args:
            yaml_str: YAML字符串
        
        Returns:
            Any: 转换后的数据
        
        Raises:
            yaml.YAMLError: YAML解析失败时抛出
        
        Examples:
            >>> yaml_handler = YAMLFileHandler()
            >>> yaml_str = "version: '1.0'\nsettings:\n  debug: true"
            >>> data = yaml_handler.from_string(yaml_str)
            >>> print(data['settings']['debug'])  # True
        """
        try:
            return yaml.safe_load(yaml_str)
        except yaml.YAMLError as e:
            self.logger.error(f"YAML解析失败: {e}")
            raise